﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace MassiveBattle.Controller
{
	public abstract partial class BaseController : MonoBehaviour, Manager.IWorker
	{
		public delegate void FDamage(ref int point);

		#region Base Data
		public Manager.BaseManager Manager { get; private set; }

		public int Id { get; protected set; }
		public string Name { get; protected set; }
		public virtual BaseStat Stat { get; protected set; }
		public string ClassName { get; private set; }
		public string Gender { get; private set; }

		public DSkills SkillData { get; protected set; }
		#endregion

		#region Position Data
		public ESide Side { get; private set; }
		public ELine Line { get; private set; }
		public int Pos { get; private set; }
		public EArea Area { get; private set; }
		public virtual Vector3 Position
		{
			get
			{
				return Vector3.zero;
			}

			protected set { }
		}
		public bool IsSidePlayer
		{
			get
			{
				return Side == ESide.PLAYER;
			}
		}
		#endregion

		#region Projectile Data
		protected GameObject projectileRes;
		protected GameObject projectileHitRes;
		protected AudioSource projectileHitAudioSource;//발사체 맞을때 사운드 용도 //
		protected ProjectileInfo projectileInfo;
		protected float projectileMyRadius;
		#endregion

		#region Effect Data
		protected DamageFont damageFont;

		private Transform pointDamage;
		protected Transform pointEffectFont;
		protected Transform pointCast;
		private Transform pointCamera;

		private IList<AnimateMaterialChange> effectHitMaterialList;
		#endregion

		#region Action Data
		public IList<BaseController> TargetList { get; private set; }
		public BaseController Target
		{
			get
			{
				return TargetList == null || TargetList.Count <= 0 ? null : TargetList[0];
			}
		}
		public bool IsTargetExist
		{
			get
			{
				return Target != null;
			}
		}

		public IDictionary<BaseController, int> PointDic { get; private set; }
		#endregion

		#region Current Data
		private Transform modelTransform;
		private GameObject modelGameObject;
		/// <summary>
		/// 제어하는 대상의 transform값.
		/// </summary>
		public Transform ModelTransform
		{
			get
			{
				return modelTransform;
			}

			set
			{
				StopTake();

				modelTransform = value;
				modelGameObject = modelTransform.gameObject;

				if ((pointDamage = BattleUtil.SearchHierarchyForBone(modelTransform, "Damage_point")) == null)
					pointDamage = modelTransform;
				if ((pointCast = BattleUtil.SearchHierarchyForBone(modelTransform, "CastPoint")) == null)
					pointCast = modelTransform;
				if ((pointEffectFont = BattleUtil.SearchHierarchyForBone(modelTransform, "HUD_Head")) == null)
					pointEffectFont = modelTransform;
				if ((pointCamera = BattleUtil.SearchHierarchyForBone(modelTransform, "Camera_Point01")) == null)
					pointCamera = modelTransform;

				effectHitMaterialList = modelTransform.GetComponentsInChildren<SkinnedMeshRenderer>(true).Select(data =>
				{
					var effect = data.GetComponent<AnimateMaterialChange>();

					if (effect == null)
						effect = data.gameObject.AddComponent<AnimateMaterialChange>();

					effect.Target = Manager.EffectHitMaterial;
					effect.Time = Manager.EffectHitTime;

					return effect;
				}).ToList();

				Position = modelTransform.position;
			}
		}
		/// <summary>
		/// 제어하는 대상의 gameObject값.
		/// </summary>
		public GameObject ModelGameObject
		{
			get
			{
				return modelGameObject;
			}

			set
			{
				ModelTransform = value.transform;
			}
		}

		private int healthPointCur;

		public EState StateType { get; private set; }

		public bool IsAlive { get; protected set; }
		protected abstract bool IsCanAction { get; }
		public virtual bool IsHealer
		{
			get
			{
				return false;
			}

			protected set { }
		}
		public bool IsCaster { get; private set; }

		public float ActionTimeLeft { get; protected set; }

		public int HealthPointCur
		{
			get
			{
				return healthPointCur;
			}

			protected set
			{
				healthPointCur = Mathf.Clamp(value, 0, Stat.baseHP.Get(Manager.Monitor));
			}
		}

		private FDamage eventDamage;
		public event FDamage EventDamage
		{
			add
			{
				if (eventDamage != null && eventDamage.GetInvocationList().Contains(value))
					return;

				eventDamage += value;
			}

			remove
			{
				eventDamage -= value;
			}
		}
		#endregion

		#region Init
		protected void Awake()
		{
			PointDic = new Dictionary<BaseController, int>();

			IsAlive = false;
		}

		protected void Init(int model, ESide side, ELine line, int pos, EArea area)
		{
			var compBattleCharInfo = ModelTransform.GetComponent<BattleCharInfo>();

			eventDamage = null;

			string className, gender;
			Utility.GetModelInfo(model, out className, out gender);
			ClassName = className;
			Gender = gender;

			Side = side;
			SetPosition(line, pos, area);

			SetState();

			IsCaster = compBattleCharInfo.isCaster;
			IsHealer = string.Equals(ClassName, "Cleric") || string.Equals(ClassName, "Chanter");

			projectileInfo = compBattleCharInfo.projectileInfo;
			projectileMyRadius = compBattleCharInfo.myRadius;

			ActionTimeLeft = Utility.GetInitialActionTime(Manager, Stat);

			InitClass();
			InitTake();
			InitFsm();

			// 무기 장착.
			BattleResManager.AttachWeaponLow(ModelGameObject, compBattleCharInfo, null, true);
		}

		protected virtual void InitClass()
		{
			if (IsCaster)
			{
				try
				{
					BattleResManager.Instance.LoadProjectile(ClassName, projectileInfo);
					projectileRes = BattleResManager.Instance.projectileResDic[ClassName].projectile;
					projectileHitRes = BattleResManager.Instance.projectileResDic[ClassName].hit;
				}
				catch (Exception e)
				{
					Debug.LogException(e);
					Debug.LogError("투사체 리소스를 로드하지 못하였습니다.");
				}
			}
		}

		protected abstract void InitTake();

		protected abstract void InitFsm();

		public void SetManager(Manager.BaseManager manager)
		{
			Manager = manager;
		}
		#endregion

		#region Logic
		public void SetPosition(ELine line, int pos, EArea area)
		{
			Line = line;
			Pos = pos;
			Area = area;
		}

		public void SetState(EState state = EState.NONE)
		{
			if (pointEffectFont == null)
				return;

			if (StateType != EState.NONE)
			{
				var effect = pointEffectFont.Find("Effect" + StateType);

				if (effect != null)
					effect.gameObject.SetActive(false);
			}

			StateType = state;

			if (StateType != EState.NONE)
			{
				Transform effect = pointEffectFont.Find("Effect" + StateType);

				if (effect == null)
				{
					effect = (Instantiate(Manager.EffectStateList[(int)StateType - 1]) as GameObject).transform;
					effect.name = "Effect" + StateType;
					effect.parent = pointEffectFont;
					effect.localPosition = Vector3.zero;
					effect.localRotation = Quaternion.identity;
					effect.localScale = Vector3.one;
				}
				else
					effect.gameObject.SetActive(true);
			}
		}

		private void Update()
		{
			if (IsAlive && Manager.IsActive && IsTakeIdle)
			{
				ActionTimeLeft -= Time.deltaTime;

				if (ActionTimeLeft <= 0f)
				{
					DecideAction();

					ActionTimeLeft = Utility.GetActionTime(Manager, Stat);
				}
			}
		}

		protected virtual void OnDestroy()
		{
			StopTake();

			SetState();

			if (damageFont != null)
			{
				damageFont.Clear();
				damageFont = null;
			}
		}

		protected abstract void OnDie();

		public void MoveToEmptyPosition(Vector3 position)
		{
			Position = position;

			FsmMain.Transit(EStateMain.forward);
		}

		private void CheckHealthPoint(int point)
		{
			if (HealthPointCur <= 0)	// 사망.
			{
				cameraidleinstant();

				IsAlive = false;

				// 전체 HP 갱신.
				Manager.UpdateHealthPoint(Side);

				OnDie();
			}
			else
			{
				CheckPhase(HealthPointCur);

				// 전체 HP 갱신.
				Manager.DecreaseHealthPoint(Side, -point);
			}
		}

		protected virtual void CheckPhase(int point) { }

		private bool CheckUseSkill()
		{
			return UnityEngine.Random.value < Utility.GetSkillProbability(Manager, this);
		}
		#endregion

		#region Action
		protected IEnumerable<BaseController> GetCandidate()
		{
			return Manager.GetList(this);
		}

		protected bool SetTarget(IEnumerable<BaseController> list)
		{
			TargetList = list.ToList();

			return IsTargetExist;
		}

		private void DecideAction()
		{
			if (IsCanAction)
				FsmMain.Transit(IsHealer ? EStateMain.heal : CheckUseSkill() ? EStateMain.skill : EStateMain.attack);
		}

		public void Damage(int point, bool isSkill, BaseController source)
		{
			if (!IsAlive || (source != null && !Manager.IsActive))
				return;

			OnDamage(ref point);

			// 공격쪽이 아군일경우. 전술 포인트 획득.
			if (source != null && source.IsSidePlayer)
			{
				Manager.IncreaseTacticsPoint(Utility.GetTacticsPointWhenAttack(Manager, source.Stat));

				Manager.Report.IncreaseAttackPoint(source.Stat.atkType.Get(Manager.Monitor), point <= HealthPointCur ? point : HealthPointCur);
			}

			// 맞은쪽이 아군일경우. 전술 포인트 획득.
			if (IsSidePlayer)
			{
				Manager.IncreaseTacticsPoint(Utility.GetTacticsPointWhenHit(Manager, Stat));

				Manager.Report.IncreaseDamagePoint(Stat.atkType.Get(Manager.Monitor), point <= HealthPointCur ? point : HealthPointCur);
			}

			// HP 갱신.
			HealthPointCur -= point;

			// 이펙트(폰트).
			if (pointEffectFont != null)
			{
				if (damageFont == null)
					damageFont = ModelGameObject.AddComponent<DamageFont>();
				damageFont.ShowDamage(false, isSkill, pointEffectFont, point, this, source, 0);
			}

			// 이펙트(재질).
			{
				foreach (var effect in effectHitMaterialList)
					effect.Change();
			}

			CheckHealthPoint(point);

			// 이펙트(UI - HP 감소).
			Manager.UI.ShowHpDecreaseEffect(Side);
		}

		private void OnDamage(ref int point)
		{
			if (eventDamage != null)
				eventDamage(ref point);
		}

		public void Heal(int point)
		{
			if (!IsAlive || !Manager.IsActive)
				return;

			// HP 갱신.
			HealthPointCur += point;

			// 이펙트(폰트).
			if (pointEffectFont != null)
			{
				if (damageFont == null)
					damageFont = ModelGameObject.AddComponent<DamageFont>();
				damageFont.ShowDamage(true, false, pointEffectFont, point, this, null, 0);

			}

			// 전체 HP 갱신.
			Manager.UpdateHealthPoint(Side);
		}

		public void Projectile(BaseController target)
		{
			if (target == null)
				return;

			if (projectileRes == null)
				return;

			var source = Manager.GameObjectPool[projectileRes].transform;
			var position = target.pointDamage.TransformPoint(target.pointDamage.localPosition);

			if (Manager.IsRaid)
			{
				if (!target.IsSidePlayer)
					position += new Vector3(UnityEngine.Random.value - 0.5f, UnityEngine.Random.value - 0.5f) * 2f;
			}

			source.gameObject.SetActive(true);
			source.parent = pointCast;
			source.localPosition = Vector3.zero;
			source.parent = null;
			source.LookAt(position);
			source.localScale = Vector3.one;
			float time = (position - source.position).magnitude / projectileInfo.speed;

			var compMassiveBattleProjectile = source.GetComponent<Projectile>();
			if (compMassiveBattleProjectile == null)
				compMassiveBattleProjectile = source.gameObject.AddComponent<Projectile>();
			compMassiveBattleProjectile.SetManager(Manager);
			compMassiveBattleProjectile.Fire(this, target, position, time, projectileInfo, projectileHitRes);
		}
		#endregion

		public void PlayProjectileHitSound(float _volume, AudioClip _clip)
		{
			if (projectileHitAudioSource == null)
				projectileHitAudioSource = ModelGameObject.AddComponent<AudioSource>();
			projectileHitAudioSource.rolloffMode = AudioRolloffMode.Custom;
			projectileHitAudioSource.playOnAwake = false;

			projectileHitAudioSource.volume = _volume;
			projectileHitAudioSource.clip = _clip;
			projectileHitAudioSource.Play();
		}
	}
}
